package com.info33.platform.business.app.service.impl;

import com.info33.platform.business.app.dao.AppTenantUserDao;
import com.info33.platform.business.app.entity.AppTenantUser;
import com.info33.platform.business.app.service.AppLoginService;
import com.info33.platform.business.app.service.AppTenantUserService;
import com.info33.platform.business.app.vo.AppTenantUserVO;
import com.info33.platform.common.enums.LoginType;
import com.info33.platform.common.exception.ResultException;
import com.info33.platform.common.mvc.vo.Result;
import com.info33.platform.common.util.RedisUtils;
import com.info33.platform.config.shiro.token.UserToken;
import com.info33.platform.system.dto.SysUserDTO;
import com.info33.platform.system.vo.SysUserVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.util.Date;

/**
 * @author debj
 * @version 1.0
 * @date 2021/8/9 16:06
 */
@Slf4j
@Service
public class AppLoginServiceImpl implements AppLoginService {

    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private AppTenantUserDao appTenantUserDao;
    @Autowired
    private AppTenantUserService appTenantUserService;

    /**
     * 定义新对象，避免 shiro 当前用户属性被变更，同时去除敏感字段
     *
     * @param token-
     * @return -
     */
    private Result<Object> excludeSensitivityInfo(String token, SysUserVO vo) {
        SysUserVO returnVo = new SysUserVO();
        returnVo.setToken(token);
        returnVo.setTrueName(vo.getTrueName());
        returnVo.setHeadImg(vo.getHeadImg());
        returnVo.setMobile(vo.getMobile());
        return Result.ok("登录成功", returnVo);
    }

    /**
     * 检测租户与用户冻结状态
     *
     * @param vo 入参
     * @return object
     */
    private void checkTenantAndLocked(AppTenantUserVO vo) {
        if (ObjectUtils.isEmpty(vo)) {
            throw new ResultException("租户不存在");
        }
        if (Boolean.FALSE.equals(vo.getTenantVO().getActivate())) {
            throw new ResultException("租户未激活");
        }
        if (Boolean.FALSE.equals(vo.getTenantVO().getHasApp())) {
            throw new ResultException("此租户 app 应用已经下架");
        }
        if (Boolean.TRUE.equals(vo.getLocked())) {
            throw new ResultException("该账号已经被冻结");
        }
    }

    @Override
    public Result<Object> passwordLogin(SysUserDTO dto) {
        redisUtils.checkPwdErrCount(dto.getLoginName(), LoginType.M_APP);
        try {
            Subject subject = SecurityUtils.getSubject();
            subject.login(new UserToken(dto.getLoginName(), dto.getPassword(), LoginType.APP_PASSWORD));
            SysUserVO vo = (SysUserVO) subject.getPrincipal();
            redisUtils.clearPwdErrCount(dto.getLoginName(), LoginType.M_APP);
            // 更新该用户在此租户下的登录时间
            AppTenantUser entity = new AppTenantUser();
            entity.setId(vo.getAppTenantUserVO().getId());
            entity.setLoginTime(new Date());
            entity.setLoginType(LoginType.APP_PASSWORD);
            appTenantUserService.updateById(entity);
            return excludeSensitivityInfo(subject.getSession().getId().toString(), vo);
        } catch (UnknownAccountException e) {
            return Result.error(e.getMessage());
        } catch (LockedAccountException e) {
            return Result.error("登录失败，该用户已被冻结");
        } catch (IncorrectCredentialsException e) {
            return Result.error(redisUtils.addPwdErr(dto.getLoginName(), LoginType.M_APP));
        } catch (AuthenticationException e) {
            return Result.error("登录失败");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(Result.RESULT_ERROR_PUBLIC_MSG);
        }
    }

    @Override
    public Result<Object> messageLogin(SysUserDTO dto) {
        // 校验验证码
        redisUtils.checkCodeByMobile(dto.getMobile(), dto.getCode());
        try {
            Subject subject = SecurityUtils.getSubject();
            subject.login(new UserToken(dto.getMobile(), dto.getCode(), LoginType.APP_MESSAGE));
            SysUserVO vo = (SysUserVO) subject.getPrincipal();
            // 更新该用户在此租户下的登录时间
            AppTenantUser entity = new AppTenantUser();
            entity.setId(vo.getAppTenantUserVO().getId());
            entity.setLoginTime(new Date());
            entity.setLoginType(LoginType.APP_MESSAGE);
            appTenantUserService.updateById(entity);
            return excludeSensitivityInfo(subject.getSession().getId().toString(), vo);
        } catch (UnknownAccountException e) {
            return Result.error(e.getMessage());
        } catch (LockedAccountException e) {
            return Result.error("登录失败，该用户已被冻结");
        } catch (AuthenticationException e) {
            return Result.error("登录失败");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(Result.RESULT_ERROR_PUBLIC_MSG);
        }
    }

    @Override
    public Result<Object> messagePushByLogin(SysUserDTO dto) {
        AppTenantUserVO tenantUserVO = appTenantUserDao.getTenantAndUserByMobile(dto);
        this.checkTenantAndLocked(tenantUserVO);
        if (ObjectUtils.isEmpty(tenantUserVO.getUserId())) {
            return Result.error("该账号暂未注册");
        }
        // 发送验证码
        return Result.ok(redisUtils.pushCodeByMobile(dto.getMobile()));
    }

    @Override
    public Result<Object> messagePushByRegister(SysUserDTO dto) {
        AppTenantUserVO tenantUserVO = appTenantUserDao.getTenantAndUserByMobile(dto);
        this.checkTenantAndLocked(tenantUserVO);
        if (!ObjectUtils.isEmpty(tenantUserVO.getUserId())) {
            return Result.error("您已经注册过");
        }
        // 发送验证码
        return Result.ok(redisUtils.pushCodeByMobile(dto.getMobile()));
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Result<Object> appRegister(SysUserDTO dto) {
        // 校验验证码
        redisUtils.checkCodeByMobile(dto.getMobile(), dto.getCode());
        // 校验是否可以注册
        AppTenantUserVO tenantUserVO = appTenantUserDao.getTenantAndUserByMobile(dto);
        this.checkTenantAndLocked(tenantUserVO);
        if (!ObjectUtils.isEmpty(tenantUserVO.getUserId())) {
            return Result.error("您已经注册过");
        }
        if (ObjectUtils.isEmpty(tenantUserVO.getUserId())) {
            appTenantUserService.register(dto, tenantUserVO.getTenantVO().getId());
            return Result.ok("注册成功");
        }
        return Result.error(Result.RESULT_ERROR_PUBLIC_MSG);
    }

}
